//do error checking
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "link.h"

#define err(h) {printf("%s\n",h); exit(1);}

void parse_config(void);
void parse_arguments(int, char**);
void parse_link(FILE *, struct tag_link *);
void parse_header(FILE *, struct tag_link *);
void parse_shell_id(FILE *, struct tag_link *);
void parse_file_location(FILE *, struct tag_link *);
void parse_local_volume(FILE *, struct tag_link *); 
void parse_network_volume(FILE *, struct tag_link *); 
void parse_flags(int, struct tag_link *);
void parse_attributes(int, struct tag_link *);
void parse_type(int, struct tag_link *);
wchar_t *read_string(FILE *);
char *linux_path(char *);
int str_read(FILE *, char *, int);

int shell_id, file_dir, directory,desc, rel_path, work_dir, cmd_line, icon, local, network;
int details = 0, only_linux = 0;
char *filename = NULL;
char *link_file = NULL;
char *drives[15][2]; //contains the map of windows drives in linux from the config file
int dtotal = 0; //total drives in the config file
int config = 0; //whether config file is present or not

int main(int argc, char *argv[]) {
	struct tag_link *link;
	FILE *fp;

	parse_arguments(argc, argv);
	parse_config(); //parse the configuration file if it exists
	if(filename) {
		if((fp=fopen(filename, "r"))==NULL) {
			printf("Could not open file %s\n",filename);
			return 1;
		}
	}
	else {
		printf("Please enter a filename or see help (by using the argument -h) for more information\n");
		return 0;
	}

	link = malloc(sizeof(*link));
	parse_link(fp, link);

	if(details) //a lot of junk is printed, so put some space for the important part
		printf("\n\n\n");
	if(!only_linux) { //we don't want just the linux paths
		if(link->directory)
			printf("Link is a directory\n");
		else if(link->file_dir)
			printf("Link is a file\n");
		else
			printf("Link is neither a file nor a directory\n");
		printf("Base path is %s\n",link->base_path);
		if(link->network)
			printf("Actual path is %s\n",link->final_path);
		if(link->desc)
			printf("Descriptive string: %S\n",link->desc_str);
		if(link->cmd_line)
			printf("Command line arguments are %S\n",link->cmd);
		if(link->work_dir)
			printf("Working directory is %S\n", link->dir);
		if(link->rel_path)
			printf("Relative path is %S\n", link->path);
		if(link->icon)
			printf("Icon path is %S\n", link->icon_str);
		printf("Linux path is: ");
	}
	printf("%s\n", linux_path(link->base_path)); //only linux path appears if -l is given
	free(link);
	fclose(fp);
	return 0;
}

//The main function which takes a link file and link structure and populates the structure
void parse_link(FILE *fp, struct tag_link *link) {
	parse_header(fp, link);

	if(shell_id)
		parse_shell_id(fp, link);

	parse_file_location(fp, link);

	if(desc) { //descriptive string
		link->desc_str = read_string(fp);
	}
	if(rel_path) { //relative path string
		link->path = read_string(fp);
	}
	if(work_dir) {
		link->dir = read_string(fp);
	}
	if(cmd_line) {
		link->cmd = read_string(fp);
	}
	if(icon) {
		link->icon_str = read_string(fp);
	}
}

void parse_header(FILE *fp, struct tag_link *link) {
	struct tag_link_header *header, h;

	header = malloc(sizeof(*header));
	fread(header, sizeof(*header), 1, fp); //must be sizeof(*header) not sizeof(header)
	//ate a whole half a hour for finding this bug

	if(feof(fp)) {
		printf("Error code %d\n",ferror(fp));
		err("Unexpected end of file at the header or the file could not be parsed further");
	}
	h = *header;
	if(h.magic!=0x4C) 
		err("File entered is not a valid shortcut\nDoesn't have the right magic number");

	if(details)
		printf("magic: %X\nflags: %X\nattributes: %X\nFile length: %X\n",h.magic, h.flags, h.attributes, h.file_length);
	link->icon_number = h.icon_number;
	if(details)
		printf("Icon number: %d\tTime 1: %X %X\n",h.icon_number, h.c1, h.c2);
	parse_flags(h.flags, link);
	parse_attributes(h.attributes, link);
	//if(details)
	//printf("Zeros are %d\t%d\n",h.zero[0], h.zero[1]);
	free(header);
}

//doesn't actually parse it, just bypasses it
void parse_shell_id(FILE *fp, struct tag_link *link) {
	unsigned short int len;
	fread(&len, sizeof(len), 1, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");
	if(details) {
		printf("\nBy-passing shell id\n");
		printf("Size of shell id is %X\n", len);
	}
	fseek(fp, len, 1); //1 - for the current position
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");
}

void parse_file_location(FILE *fp, struct tag_link *link) {
	struct tag_file_location_info *file_location, fl;

	file_location = malloc(sizeof(*file_location));
	fread(file_location, sizeof(*file_location), 1, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");

	fl = *file_location;

	if(details) {
		printf("\nFile Location Info\n");
		printf("Length of the structure: %d\n",fl.length);
		printf("Offset %X\n",fl.offset);
	}

	local = network = 0;
	link->local = link->network = 0;
	if(fl.flags&1) {
		link->local = 1;
		local = 1;
		if(details)
			printf("Available on local volume\n");
	}
	if(fl.flags&2) {
		link->network = 1;
		network = 1;
		if(details)
			printf("Available on a network\n");
	}

	if(local)
		parse_local_volume(fp, link); 

	if(network) //network path
		parse_network_volume(fp, link);
	free(file_location);
}

void parse_local_volume(FILE *fp, struct tag_link *link) {
	struct tag_local_volume_table *local_volume, lv;
	char *base_path, *volume_label;

	local_volume = malloc(sizeof(*local_volume));
	fread(local_volume, sizeof(*local_volume), 1, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");

	lv = *local_volume;
	if(details) {
		printf("\nLocal volume: \n");
		//printf("Length of local volume %d\n", lv.length);
		//printf("Type of volume %d\n", lv.type);
		printf("Volume serial number %X\n", lv.serial_number);
		//printf("Offset of volume name %d\n", lv.volume_name);
		printf("Volume is ");	
	}
	parse_type(lv.type, link);

	volume_label = malloc(sizeof(*volume_label)*255);
	str_read(fp, volume_label, 255);
	if(details)
		printf("Volume label is %s\n",volume_label);
	base_path = malloc(sizeof(base_path)*255);

	str_read(fp, base_path, 255);
	link->base_path = base_path;
	if(details)
		printf("\nBase path is %s\n",base_path);
	free(local_volume);
}

void parse_network_volume(FILE *fp, struct tag_link *link) {
	struct tag_network_volume_table *network_volume, nv;
	char *final_path, *share_name;

	network_volume = malloc(sizeof(*network_volume));
	fread(network_volume, sizeof(*network_volume), 1, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");

	nv = *network_volume;

	if(details) {
		printf("\nNetwork volume: \n");
		//printf("Length %d\n", nv.length);
	}
	share_name = malloc(sizeof(*share_name)*255);
	str_read(fp, share_name, 255);
	link->share_name = share_name;
	if(details)
		printf("Share name: %s\n",share_name);

	//if the network structure is present, the final path string should be read
	final_path = malloc(sizeof(*final_path)*255);
	str_read(fp, final_path, 255);
	link->final_path = final_path;
	if(details)
		printf("Final Path: %s\n",final_path);
	free(network_volume);
}

void parse_flags(int flags, struct tag_link *link) {
	shell_id = file_dir = desc = rel_path = work_dir = cmd_line = icon = 0;

	if(flags&1)
		shell_id = 1;
	if(flags&2)
		file_dir = 1;
	if(flags&4)
		desc = 1;
	if(flags&8)
		rel_path = 1;
	if(flags&16)
		work_dir = 1;
	if(flags&32)
		cmd_line = 1;
	if(flags&64)
		icon = 1;

	link->shell_id = shell_id;
	link->file_dir = file_dir;
	link->desc = desc;
	link->rel_path = rel_path;
	link->work_dir = work_dir;
	link->cmd_line = cmd_line;
	link->icon = icon;

	if(details)
		printf("\nFlags say: \n");

	if(shell_id&&details)
		printf("Has shell id structure\n");
	if(file_dir&&details)
		printf("Points to a file or directory\n");
	if(desc&&details)
		printf("Has descriptive string\n");
	if(rel_path&&details)
		printf("Has relative path\n");
	if(work_dir&&details)
		printf("Has working directory\n");
	if(cmd_line&&details)
		printf("Has command line arguments\n");
	if(icon&&details)
		printf("Has an icon\n");
}

void parse_attributes(int attributes, struct tag_link *link) {
	link->read_only = link->hidden = link->sys_file = link->volume_label = link->directory = link->archive = link->encrypted = link->normal = 0;

	if(details)
		printf("\nAttributes say: \n");
	if(attributes&1) {
		link->read_only = 1;
		if(details)
			printf("Read only\n");
	}
	if(attributes&2) {
		link->hidden = 1;
		if(details)
			printf("Hidden\n");
	}
	if(attributes&4) {
		link->sys_file = 1;
		if(details)
			printf("System File\n");
	}
	if(attributes&8) {
		link->volume_label = 1;
		if(details)
			printf("Volume label\n");
	}
	if(attributes&16) {
		link->directory = 1;
		directory = 1;
		if(details)
			printf("Directory\n");
	}
	if(attributes&32) {
		link->archive = 1;
		if(details)
			printf("Archive\n");
	}
	if(attributes&64) {
		link->encrypted = 1;
		if(details)
			printf("Encrypted\n");
	}
	if(attributes&128) {
		link->normal = 1;
		if(details)
			printf("Might be normal\n");
	}
	if(attributes&256) {
		link->temporary = 1;
		if(details)
			printf("Temporary\n");
	}
	//there are others left too
}

void parse_type(int type, struct tag_link *link) {
	if(details) {
		switch(type) {
		case 0: printf("Unknown\n"); break;
		case 1: printf("No root directory\n"); break;
		case 2: printf("Removable media\n"); break;
		case 3: printf("Fixed media like hard disk\n"); break;
		case 4: printf("Remote location\n"); break;
		case 5: printf("CD-ROM\n"); break;
		case 6: printf("Ram drive\n"); break;
		default: printf("Unknown type\n");
		}
	}
}


//reads the unicode string pointed by the file pointer
//assumes the first unsigned short integer is the length of the string
//used for icon, command line, paths etc.
//linux wchar_t is 32 bit signed integer not the 16 bit that we want
//so, am reading 2 chars and merging them into wchar_t
wchar_t * read_string(FILE *fp) { 
	int i, j;
	char str_len[2], *s;
	wchar_t *str;

	//getting the length of the string
	fread(str_len, 2, 1, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");
	//printf("str_len[0] = %x\tstr_len[1] = %x\n",str_len[0], str_len[1]);
	i = str_len[1]+8*str_len[0];
	//if(details)
	//printf("Length of string is %x\n",i);
	s = malloc(sizeof(*s)*(2*i+1));
	str = malloc(sizeof(*str)*(i+1));

	fread(s, sizeof(*s), 2*i, fp);
	if(ferror(fp))
		err("Unexpected end of file or the file could not be parsed further");

	s[2*i] = '\0';
	for(j=0; j<2*i; j+=2) {
		*str = s[j]<<8;
		*str += s[j+1];
		//printf("%C ", *str);
		str++;
	}
	*str = '\0';
	free(s);
	//printf("\n");
	return str-i;
}

//reads a string till \0 occurs, even if there are spaces or newlines
//returns number of characters read and -1 if there is an error
int str_read(FILE *fp, char *str, int len) {
	int i = 0;
	while((*str = fgetc(fp))!='\0') {
		str++;
		i++;
		if(i>len) {
			if(details)
				printf("Too big string\n");
			return -1;
		}
	}
	*str = '\0';
	return i;
}



//don't remember how to use getopt and am hence reinventing it
//but its not as powerful :(
void parse_arguments(int argc, char *argv[]) {
	details = 0;
	char *prog_name = *argv++;
	argc--;
	while(argc--) {
		if(!strcmp(*argv, "-d")||!strcmp(*argv, "-D")) {
			details = 1;
			argv++;
			break;
		}
		else if(!strcmp(*argv, "-l")||!strcmp(*argv, "-L")) {
			only_linux = 1;
			details = 0;
			argv++;
			break;
		}
		else if(!strcmp(*argv, "-c")||!strcmp(*argv, "-C")) {
			//printf("Link file is %s\n", *(argv+1));
			//strcpy(link_file, *(argv+1));
			link_file = *(argv+1);
			//printf("Link file is %s\n", link_file);
			argv+=2;
			argc--;
		}
		else if(!strcmp(*argv, "-h")||!strcmp(*argv, "-H")) {
			printf("Use %s [-d l] shortcut to get the details of the link\nThe option -d will give the complete information contained in the shortcut file\nThe option -l will give the posix path of the file\n", prog_name);
			exit(0);
		}
	}
	filename = *argv;
}

//converts a windows path into linux path including the drives if the drives are mapped in linux and are specified
//in the config file
char *linux_path(char *path) {
	int i;
	char drive[3]; //the drive of the path if present
	char *lpath, *l;

	drive[0] = *path;
	drive[1] = *(path+1);
	drive[2] = '\0';

	//remove the F: and replace it with /F/ or something depending on the config file
	for(i=0; i<dtotal; i++) {
		if(!strcmp(drives[i][0], drive)) {
			lpath = malloc(sizeof(*lpath)*strlen(path)+strlen(drives[i][1])-2);
			path+=2;
			strcpy(lpath, drives[i][1]);
			l = lpath+strlen(drives[i][1]);
			break;
		}
	}

	//if there is no drive match, allocate only the memory required by the path
	if(i==dtotal) {
		lpath = malloc(sizeof(*lpath)*strlen(path));
		l = lpath;
	}

	//printf("Remaining path %s\n",path);
	//remove all the \ and make them /
	while(*path) {
		if(*path=='\\')
			*l = '/';
		else
			*l = *path;
		path++;
		l++;
	}
	*l = '\0';
	//printf("Path is %s\n", lpath);
	return lpath;
}

//parses config file in which the entries should be like:
//g: folder_for_mapped_g_drive
//etc. Not much data validation yet
void parse_config(void) {
	//FILE *fp = fopen(link_file, "r");
	FILE *fp;
	char *drive, *linux_path, *line, *input_line;
	int i = 0;

	if(link_file==NULL)
		fp = fopen("link.conf", "r");
	else
		fp = fopen(link_file, "r");
		
	if(fp==NULL) {
		if(details)
			printf("No config file found\n");
		return;
	}
	input_line = malloc(sizeof(*input_line)*255); //don't think anybody's linux path will be more big that this

	while(fgets(input_line, sizeof(*input_line)*255, fp)!=NULL&&strlen(input_line)>2) {
		line = input_line;
		line[strlen(line)-1] = '\0'; //remove the \n
		if(line[strlen(line)-1] == '\r') //gcc tends to put that too in windows!
			line[strlen(line)-1] = '\0';

		drive = malloc(sizeof(*drive)*3); //new memory should be allocated for each drive
		linux_path = malloc(sizeof(*linux_path)*255);

		*drive = *line;
		*(drive+1) = *(line+1);
		*(drive+2) = '\0';
		line+=2;
		if(*line!=' ') {
			printf("Invalid config file, no space after windows drives\n");
			fclose(fp);
			return;
		}
		while(*line==' ')
			line++; //ignore all the spaces
		strncpy(linux_path, line, 255);
		//printf("Drive mapped for %s is %s\n",drive, linux_path);
		drives[i][0] = drive;
		drives[i][1] = linux_path;
		i++;
	}
	dtotal = i;
	//free(input_line); //why doesn't this work in gcc???
	fclose(fp);
	//printf("Total drives mapped is %d\n", dtotal);
	/*for(i=0; i<dtotal; i++)
	  printf("%s\t%s\n", drives[i][0], drives[i][1]);*/
}
